/*
 * jQuery Suggestions UI Widget 1.0
 * Copyright (c) 2012 Kapil Kashyap
 *
 * Depends:
 *   - jQuery 1.6+
 *   - jQuery UI 1.8 widget factory
 *
 * Dual licensed under the MIT and GPL licenses:
 *   - http://www.opensource.org/licenses/mit-license.php
 *   - http://www.gnu.org/licenses/gpl.html
 */
(function($) {
	$.widget( "kk.suggestions", {
		// default options
	    options: {
	    	triggerOn: "@",
	    	url: null,
	    	itemTemplate: null,
			identifierProperty: "id",
			valueProperty: null,
			itemsPerPage: 10,
			dropdownWidth: null,
			closeBtnRequired: false,
			footerRequired: false,
			keyNavigation: {
				up_down: true,
				next_prev: true
			},
			autoHeight: false,
			increaseHeightAfter: 75, // characters
			customEventPrefix: null,
			cache: false
	    },
	    _create: function() {
	    	var wrapper = null,
	    		hiddenInputElement = null,
	    		ddw = 0;

	    	// set widget properties
	    	this._setWidgetProperties();

	    	// create a wrapper for the target element
	    	wrapper = $( "<div></div>" ).addClass( this.wp.widgetBaseClass + this.wp.hyphen + this.wp.wrapperLabel );
	    	// wrap the element with the wrapper
	    	this.element.wrap( wrapper.width(this.element.width() + 4));

	    	// set widget state
	    	this._setWidgetState();

	    	// add widget base class
	    	this.element.addClass( this.wp.widgetBaseClass );

	        // if dropdown width is specified use that, else calculate dropdown width dynamically.
	        if(this.options.dropdownWidth) {
	        	ddw = parseInt(this.options.dropdownWidth, 10);
	        	this.options.dropdownWidth = ddw ? ddw : 0;
	        }

	        // create a hidden input element
	        hiddenInputElement = $( "<input></input>", {
    				"type": "hidden",
    				"value": "",
    				"name": "x_suggestions_hidden_input",
    				"class": "x-" + this.wp.widgetBaseClass + "-hidden-input"
    			});

	        // append the hidden input element to the wrapper
	        this.element.parent().append(hiddenInputElement);

        	// bind various events to the element
        	this._bindEvents();

        	// create a template for the paginated drop down footer
        	this._constructPaginationFooterTemplate();
	    },
	    _init: function(config) {
	    	config = config || this.options;
	    	$.ajaxSetup({cache: config.cache});
	    },
	    _setWidgetProperties: function() {
	    	if(this.options.customEventPrefix) {
	    		this.widgetEventPrefix = this.options.customEventPrefix;
	    	}
	    	this.wp = {};
	    	this.wp.widgetBaseClass 		= "suggestions";
	    	this.wp.selectedItemClass 		= "selected-item";
	    	this.wp.space 					= " ";
	    	this.wp.hyphen 					= "-";
	    	// labels
	    	this.wp.ofLabel 				= "of"; // This should be internationalised
	    	this.wp.wrapperLabel 			= "wrapper";
	    	this.wp.containerLabel 			= "container";
	    	this.wp.itemLabel 				= "item";
	    	this.wp.footerLabel 			= "footer";
	    	this.wp.footerContentLabel 		= "footer-content";
	    	this.wp.footerInfoLabel 		= "footer-info";
	    	this.wp.footerLeftNavLabel 		= "footer-left-nav";
	    	this.wp.footerRightNavLabel 	= "footer-right-nav";
	    	this.wp.closeBtnLabel 			= "close-button";
	    	// custom events
	    	this.wp.itemSelectedEvent 		= "itemselected";
	    	// regular expressions for firing ajax call
	    	this.wp.fireAjaxRegEx 			= new RegExp( "\\s" + this.options.triggerOn + "\\w+" ); 
	    	this.wp.fireAjaxRegEx2 			= new RegExp( this.options.triggerOn + "\\w+" ); 
	    	// regular expressions to update the hidden input field
	    	this.wp.contentEditableRegEx 	= new RegExp("\\s?contenteditable=(\"false\"|false)\\s?", "ig"); // replace with empty string ""
	    	this.wp.breakLineRegEx 			= new RegExp("<br>", "ig"); // replace with empty string ""
	    	this.wp.openingTagRegEx 		= new RegExp("\\<button\\s?|\\<b\\s?", "ig"); // replace with empty string ""
	    	this.wp.closingTagRegEx 		= new RegExp("</button>|</b>", "ig"); // replace with "]"
	    	this.wp.pidRegEx 				= new RegExp("\s?pid=\"", "ig"); // replace with "@["
	    	this.wp.nbspRegEx 				= new RegExp("&nbsp;", "g"); // replace with space " "
	    	this.wp.delimeterRegEx 			= new RegExp("\">", "g"); // replace with ":"
	    	this.wp.typeRegEx 				= new RegExp("\\s?type=submit\\s?", "ig"); // replace with empty string ""
	    	this.wp.spanRegEx 				= new RegExp("<span contenteditable=(\"true\"|true)>|</span>", "ig"); // replace with empty string ""
	    	// regular expressions for removing the mailto link in IE8 and below
	    	this.wp.mailToPrefixRegEx		= new RegExp("<a href=\"mailto:\\w+\\.?(\\w+)?\\@\\w+\">", "ig"); // replace with empty string ""
	    	this.wp.mailToSuffixRegEx		= new RegExp("</a>", "ig"); // replace with empty string ""
	    	// labels for regular expressions replacements
	    	this.wp.emptyString 			= "";
	    	this.wp.enclosurePrefix 		= "@[";
	    	this.wp.enclosureDelimeter		= ":";
	    	this.wp.enclosureSuffix 		= "]";
        	// keep track of the elements original height
	    	this.wp.elOriginalHeight 		= this.element.height();
	    },
	    _bindEvents: function() {
	    	var self = this,
	    		el = self.element,
		    	selection = null;

			el.keydown(function( event ) {
				if(self.options.footerRequired && self.options.keyNavigation.next_prev && event.keyCode == 37) {
					event.preventDefault();
					self._prevPage(event, self);
					return;
				}
				else if(self.options.footerRequired && self.options.keyNavigation.next_prev && event.keyCode == 39) {
					event.preventDefault();
					self._nextPage(event, self);
					return;
				}
				// fix for mozilla.
				// When the highlighted node is selected, backspace and delete button misbehave
				if($.browser.mozilla) {
					if(event.keyCode == 8 || event.keyCode == 46) {
						selection = document.getSelection();
						if(selection && selection.toString() != "") {
							if(event.keyCode == 8) {
								event.preventDefault();
							}
							selection.deleteFromDocument();
							self._updateHiddenInput();
							el.focus();
						}
					}
				}
				if(self.options.autoHeight) {
					self._setElHeight(el.text().length);
				}
			});

			el.keypress(function( event ) {
				if(event.keyCode == 13) {
    				event.preventDefault();
    				if($( el.parent().find( "." + self.wp.selectedItemClass ) ).length > 0) {
    					self._updateContent(self._getSelectedItemObject());
    					self._cleanup();
    					self._fireEvent(self.wp.itemSelectedEvent, event);
    				}
    				return;
				}
			});

    		el.keyup(function( event ) {
    			var selectedItem = $( el.parent().find( "." + self.wp.selectedItemClass ) ),
    				newItemIndex = null,
    				highlightItemIndex = null,
    				range = null,
    				_value = el.text();

    			// clean up the dropdown and return, if input element is empty or Esc key is pressed.
    			if(_value == "" || event.keyCode == 27) {
    				if(_value == "") {
    					el.siblings( ".x-" + self.wp.widgetBaseClass + "-hidden-input" ).val( "" );
    					el.html( "" );
    					self._resetWidgetState();
    				}
    				self._cleanup();
    				return;
    			}

    			// pro-actively update the hidden field
    			self._updateHiddenInput();
    			if(self.options.footerRequired && self.options.keyNavigation.up_down && event.keyCode == 38) {
    				highlightItemIndex = self._getWidgetState().endIndex - self._getWidgetState().startIndex + 1;
    				newItemIndex = selectedItem.prev().index();

    				if(newItemIndex != -1) {
    					highlightItemIndex = newItemIndex + 1;
    				}
    				self._highlightItem( highlightItemIndex );
    				return;
    			}
    			else if(self.options.footerRequired && self.options.keyNavigation.up_down && event.keyCode == 40) {
    				highlightItemIndex = 1;
    				newItemIndex = selectedItem.next().hasClass( self.wp.widgetBaseClass + self.wp.hyphen + self.wp.footerLabel ) ? -1 : selectedItem.next().index();

    				if(newItemIndex != -1) {
    					highlightItemIndex = newItemIndex + 1;
    				}
    				self._highlightItem( highlightItemIndex );
    				return;
    			}
    			else {
   	    			if(_value.match( self.wp.fireAjaxRegEx ) || _value.search( self.wp.fireAjaxRegEx2 ) != -1) {
    	    			var offset = 0,
    	    				preCaretTextRange = null,
    	    				subString = "",
    	    				triggerIndex = -1,
    	    				query = "",
    	    				getOffset = function(o, previousSibling) {
    	    					if(!previousSibling) {
    	    						return o;
    	    					}

    	    					if(previousSibling.innerText || previousSibling.innerHTML) {
    	    						o = o + (previousSibling.innerText || previousSibling.innerHTML).length;
    	    					}
    	    					else if(previousSibling.data) {
    	    						o = o + previousSibling.data.length;
    	    					}
    	    					return getOffset(o, previousSibling.previousSibling);
    	    				};

    	    		    if(window.getSelection) { // non IE Browsers
    	    		        range = window.getSelection().getRangeAt(0);
    	    		        offset = getOffset(range.startOffset, range.endContainer.previousSibling);
    	    		    }
    	    		    else if(document.selection) { // IE
    	    		        range = document.selection.createRange();
    	    		        preCaretTextRange = document.body.createTextRange();
    	    		        preCaretTextRange.moveToElementText(this);
    	    		        preCaretTextRange.setEndPoint("EndToEnd", range);
    	    		        offset = preCaretTextRange.text.length;
    	    		    }

    	    		    subString = _value.substring(0, offset),
    	    			triggerIndex = subString.lastIndexOf( self.options.triggerOn ),
    	    			query = triggerIndex != -1 ? subString.substring(triggerIndex + 1) : "";
    	    			if(query != "") {
        					self._fetchData(query);
        				}
        				else {
        					self._cleanup();
        				}
    	    		}
    	    		else {
    	    			self._cleanup();
    	    		}
    			}
    		});
			// remove the dropdown and clean up the dom.
			el.blur(function( event ) {
				setTimeout(function() {
					if(el.is(":focus")) {
						return false;
					}
					self._resetWidgetState();
					self._cleanup();
				}, 200);
			});
			// Fix for IE9
			el.click(function( event ) {
				// Refer - http://stackoverflow.com/questions/12659608/how-to-prevent-converting-ab-to-a-mailto-link-in-contenteditable-div-in-ie
				if($.browser.msie) {
					try {
						document.execCommand("AutoUrlDetect", false, false);
					}
					catch( e ) {
						// will fail because IE8 and less do not support 'AutoUrlDetect'
						// and I did not want to do version specific checks.
					}
				}
			});
	    },
	    _setElHeight: function(contentLength) {
			var calculatedHeight = null,
				multiple = null;

			if(contentLength > 1) {
				multiple = Math.floor(contentLength / this.options.increaseHeightAfter);
				calculatedHeight = this.wp.elOriginalHeight + (multiple * 20);
				if(calculatedHeight != this.element.height()) {
					this.element.height( calculatedHeight );
				}
			}
	    },
	    _updateContent: function(o) {
	    	var self = this,
	    		el = self.element,
	    		queryTemplate = this.options.triggerOn + self._getWidgetState().query,
	    		elValue = el.html(),
	    		queryTemplateIndex = elValue.indexOf( queryTemplate ),
		    	updatedElValue = null;

	    	elValue = $.trim( elValue );
	    	if($.browser.mozilla) {
	    		// Fix for deleting content using backspace in FireFox
	    		// Refer: http://stackoverflow.com/questions/2239821
	    		updatedElValue = elValue.replace(queryTemplate, "<button pid=\"" + o[self.options.identifierProperty] + "\" contentEditable=\"false\"><span contenteditable=\"true\">" + o[self.options.valueProperty] + "</span></button>&nbsp;");
	    	}
	    	else {
	    		// Fix for a strange issue seen only in Internet Explorer.
	    		// issue: in the value returned by jQuery.html the trailing space
	    		// gets truncated rather than getting converted to '&nbsp;'
	    		if($.browser.msie) {
	    			if(elValue.charAt(queryTemplateIndex - 1) == " ") {
	    				elValue = elValue.substring(0, queryTemplateIndex - 1) + "&nbsp;" + elValue.substring(queryTemplateIndex, elValue.length + 6);
	    			}
	    		}
	    		updatedElValue = elValue.replace(queryTemplate, "<button pid=\"" + o[self.options.identifierProperty] + "\" contentEditable=\"false\">" + o[self.options.valueProperty] + "</button>&nbsp;") + (!$.browser.msie ? "<br>" : "");
	    	}
	    	el.html( updatedElValue );
	    	self._updateHiddenInput(o[self.options.valueProperty]);
	    	el.focus();
	    },
	    _updateHiddenInput: function(insertedText) {
	    	var hiddenInput = this.element.siblings( ".x-" + this.wp.widgetBaseClass + "-hidden-input" ),
	    		elValue = this.element.html(),
	    		transformedElValue = null,
	    		caretPosition = insertedText ? (this.element.text().indexOf(insertedText) + insertedText.length + 1) : 0;

	    	// fix for IE8 and below versions
	    	// Refer - http://stackoverflow.com/questions/12659608/how-to-prevent-converting-ab-to-a-mailto-link-in-contenteditable-div-in-ie
			if($.browser.msie && parseInt($.browser.version, 10) <= 8) {
				elValue = elValue.replace( this.wp.mailToPrefixRegEx, this.wp.emptyString ).replace( this.wp.mailToSuffixRegEx, this.wp.emptyString );
			}
	    	transformedElValue = elValue.replace( this.wp.spanRegEx, this.wp.emptyString )
								    	.replace( this.wp.typeRegEx, this.wp.emptyString )
								    	.replace( this.wp.breakLineRegEx, this.wp.emptyString )
	    								.replace( this.wp.contentEditableRegEx, this.wp.emptyString )
	    								.replace( this.wp.pidRegEx, this.wp.enclosurePrefix )
	    								.replace( this.wp.delimeterRegEx, this.wp.enclosureDelimeter )
	    								.replace( this.wp.openingTagRegEx, this.wp.emptyString )
	    								.replace( this.wp.closingTagRegEx, this.wp.enclosureSuffix )
	    								.replace( this.wp.nbspRegEx, this.wp.space );

	    	hiddenInput.val( $( "<div></div>" ).html( transformedElValue ).text() );
	    	caretPosition > 0 && (this._setCaretPositionAfter( insertedText, caretPosition ));
	    },
	    _setCaretPositionAfter: function(insertedText, caretPosition) {
			var range = null,
				childNodes = null,
				startPosition = 0,
				selectedNode = null,
				preCaretTextRange = null,
				selection = null;

			this.element.focus();
		    if(window.getSelection) { // IE9 and other Browsers
		    	selection = window.getSelection();
		        range = selection.getRangeAt(0);
		        childNodes = this.element[0].childNodes;
		        if(childNodes.length > 0) {
		        	$.each(childNodes, function(index, node) {
		        		if(node.textContent && insertedText == node.textContent) {
		        			startPosition = index;
		        		}
		        	});
		        	try {
		        		range.setStart(childNodes[startPosition + 1], 1);
		        		range.collapse(true);
		        		selection.removeAllRanges();
		        		selection.addRange(range);
		        	}
		        	catch( e ) {
		        		if(window.console) {
		        			console.error( e );
		        		}
		        	}
		        }
		    }
	    },
	    _nextPage: function(e, self) {
	    	var widgetState = self._getWidgetState();
	    	self.element.focus();
    		if(widgetState.page < widgetState.maximumNumberOfPages) {
    			widgetState.page++;
    			self._paginate();
    			self.element.parent().find( "." + self.wp.widgetBaseClass + self.wp.hyphen + self.wp.containerLabel ).html( widgetState.html );
    			self._updateFooterInfo();
    			self._bindDropdownComponentEvents();
    			self._highlightItem(1);
    			self._markAllItems();
    		}
    	},
    	_prevPage: function(e, self) {
    		var widgetState = self._getWidgetState();
    		self.element.focus();
    		if(widgetState.page > 1) {
    			widgetState.page--;
    			self._paginate();
    			self.element.parent().find( "." + self.wp.widgetBaseClass + self.wp.hyphen + self.wp.containerLabel ).html( widgetState.html );
    			self._updateFooterInfo();
    			self._bindDropdownComponentEvents();
    			self._highlightItem(1);
    			self._markAllItems();
    		}
    	},
	    _fetchData: function(query) {
	    	var self = this,
	    		widgetState = self._getWidgetState();

	    	if(self.options.url) {
	    		widgetState.query = query;
	    		$.getJSON(self.options.url, {"q": query}, function( data ) {
	    			widgetState.fetchedData = widgetState.data = data;
	    			self._constructDropDown();
	    		});
	    	}
	    },
	    _constructDropDown: function() {
	    	var data = this._getWidgetState().data;

	    	this._cleanup();
			if(data == null || data.length == 0) {
				this._resetWidgetState();
				return;
			};
			// if we have specified a template, then construct the html structure using the template.
			if(this.options.itemTemplate) {
				this._getWidgetState().data = this._constructHtmlUsingTemplate();
			}
			this._paginate();
			this._createDropdownComponents();
			this._updateFooterInfo();
			this._bindDropdownComponentEvents();
			this._highlightItem(this._getWidgetState().highlightedIndex);
			this._markAllItems();
	    },
	    _constructHtmlUsingTemplate: function() {
	    	var self = this,
	    		data = self._getWidgetState().data,
	    		constructedTemplate = [];

			if(!data) {
				var errorMsg = "data is required.";
				if(window.console) {
					console.error ? console.error( errorMsg ) : console.log( errorMsg );
					return;
				}
				alert( errorMsg );
				return;
			}
			$( data ).each(function(index, item) {
				var templateClone = $( "<div></div>" ).html( self.options.itemTemplate ).html();
				$.each(item, function(k, v) {
					var templateKey = "{@:" + k + "}",
						regEx = new RegExp(templateKey, "g");
					
					if(self.options.itemTemplate.indexOf( templateKey ) != -1) {
						templateClone = templateClone.replace( regEx, v );
					}
				});
				constructedTemplate.push( templateClone );
			});
			return constructedTemplate;
		},
		_paginate: function() {
			var self = this,
				widgetState = self._getWidgetState(),
				ipp = self.options.itemsPerPage,
				threshold = null,
				paginatedItems = [];

			if(widgetState.data && widgetState.data.length) {
				// reset the page in case there is a change in the length of fetched results
				if(widgetState.data.length != widgetState.totalResults) {
					widgetState.page = 1;
					widgetState.highlightedIndex = 1;
				}
				widgetState.totalResults = widgetState.data.length;
			}
			widgetState.maximumNumberOfPages = Math.floor( widgetState.totalResults / ipp );
			if(widgetState.totalResults % ipp > 0) {
				widgetState.maximumNumberOfPages++;
			}
			if(widgetState.totalResults > 0) {
				threshold = ipp * widgetState.page;
				widgetState.startIndex = (threshold - ipp);
				for(var z = (threshold - ipp); z < threshold; z++) {
					if(z == widgetState.totalResults) {
						break;
					}
					paginatedItems.push(widgetState.data[z]);
				}
				widgetState.endIndex = --z;
			}
			if(widgetState.maximumNumberOfPages > 1) {
				paginatedItems.push( widgetState.paginationFooter );
			}
			widgetState.html = paginatedItems.join( "" );
		},
	    _createDropdownComponents: function() {
	    	var self = this,
	    		pos = self.element.parent().position(),
	    		$dropdown_close_Btn = $("<div></div>", {
					"class": self.wp.widgetBaseClass + self.wp.hyphen + self.wp.closeBtnLabel,
					"html": "<div class=\"x-close-btn-icon\"/>",
					"click": function( event ) {
	    				event.preventDefault();
	    				self._cleanup();
	    			}
				}),
				$dropdown_container = $("<div></div>", {
					"class": self.wp.widgetBaseClass + self.wp.hyphen + self.wp.containerLabel,
					"width": self.options.dropdownWidth > 0 ? self.options.dropdownWidth : self.element.parent().width(),
					"html": self._getWidgetState().html
				});

			self.element.parent().append( $dropdown_container );
			$dropdown_container.position({
				my: "top",
				at: "bottom",
				of: self.element.parent(),
				offset: "0 -1"
			}).css("left", pos.left + "px");
			if(self.options.closeBtnRequired) {
				self.element.parent().append( $dropdown_close_Btn );
				$dropdown_close_Btn.position({
					my: "left top",
					at: "right top",
					of: $dropdown_container,
					offset: "-5 -5"
				});
			}
	    },
	    _updateFooterInfo: function() {
	    	if(this.options.footerRequired) {
	    		var widgetState = this._getWidgetState(), 
	    			_html = null;
	    		if(widgetState.maximumNumberOfPages > 1) {
	    			_html = (widgetState.startIndex + 1) + this.wp.space + this.wp.hyphen + this.wp.space + (widgetState.endIndex + 1) + this.wp.space + this.wp.ofLabel + this.wp.space + widgetState.totalResults;
	    			this.element.parent().find( "." + this.wp.widgetBaseClass + this.wp.hyphen + this.wp.footerInfoLabel ).html( _html );
	    		}
	    	}
	    },
	    _bindDropdownComponentEvents: function() {
	    	var self = this,
	    		parent = self.element.parent();
	    	// bind footer events only if footer is required.
	    	if(self.options.footerRequired) {
	    		parent.find( "." + self.wp.widgetBaseClass + self.wp.hyphen + self.wp.footerLabel ).bind("click dblclick", function( e ) {
	    			self.element.focus();
	    		});
	    		parent.find( "." + self.wp.widgetBaseClass + self.wp.hyphen + self.wp.footerRightNavLabel ).bind("click dblclick", function( e ) {
	    			self._nextPage(e, self);
	    		});
	    		parent.find( "." + self.wp.widgetBaseClass + self.wp.hyphen + self.wp.footerLeftNavLabel ).bind("click dblclick", function( e ) {
	    			self._prevPage(e, self);
	    		});
	    	}
	    	parent.find( "." + self.wp.widgetBaseClass + self.wp.hyphen + self.wp.containerLabel + " > div" ).each(function(index, item) {
	    		if(!$( item ).hasClass( self.wp.widgetBaseClass + self.wp.hyphen + self.wp.footerLabel )) {
	    			$( item ).click(function(e) {
	    				self._highlightItem(index + 1);
	    				self._updateContent(self._getSelectedItemObject());
						self._cleanup();
						self._fireEvent(self.wp.itemSelectedEvent, e);
	    			});
	    		}
	    	});
	    },
		_constructPaginationFooterTemplate: function() {
	    	if(this.options.footerRequired) {
		    	var widgetState = this._getWidgetState();
		    	widgetState.paginationFooter = '<div class="' + this.wp.widgetBaseClass + this.wp.hyphen + this.wp.footerLabel + '">';
					widgetState.paginationFooter += '<div class="' + this.wp.widgetBaseClass + this.wp.hyphen + this.wp.footerContentLabel + '">';
						widgetState.paginationFooter += '<span class="' + this.wp.widgetBaseClass + this.wp.hyphen + this.wp.footerLeftNavLabel + '">&lt;</span>';
						widgetState.paginationFooter += '<span class="' + this.wp.widgetBaseClass + this.wp.hyphen + this.wp.footerInfoLabel + '"></span>';
						widgetState.paginationFooter += '<span class="' + this.wp.widgetBaseClass + this.wp.hyphen + this.wp.footerRightNavLabel + '">&gt;</span>';
					widgetState.paginationFooter += '</div>';
				widgetState.paginationFooter += '</div>';
	    	}
		},
		_highlightItem: function(index) {
			// making sure that the already selected-item is removed first
			this._removeItemHighlight();
			this._getWidgetState().highlightedIndex = index;
			this.element.parent().find( "." + this.wp.widgetBaseClass + this.wp.hyphen + this.wp.containerLabel + " > div:nth-child( " + index + " )" ).addClass( this.wp.selectedItemClass );
		},
		_markAllItems: function() {
			var self = this,
				items = this.element.siblings( "." + this.wp.widgetBaseClass + this.wp.hyphen + this.wp.containerLabel ).children();
			items.each(function(index, item) {
				if(!$( item ).hasClass( self.wp.widgetBaseClass + self.wp.hyphen + self.wp.footerLabel )) {
					$( item ).addClass( self.wp.widgetBaseClass + self.wp.hyphen + self.wp.itemLabel );
				}
			});
		},
		_removeItemHighlight: function(selectedItem) {
			if(!selectedItem) {
				selectedItem = this.element.parent().find("." + this.wp.widgetBaseClass + this.wp.hyphen + this.wp.containerLabel + " > div." + this.wp.selectedItemClass);
			}
			selectedItem.removeClass( this.wp.selectedItemClass );
		},
		_cleanup: function() {
			if(this.options.closeBtnRequired) {
				this.element.parent().find( "." + this.wp.widgetBaseClass + this.wp.hyphen + this.wp.closeBtnLabel ).remove();
			}
			this.element.parent().find( "." + this.wp.widgetBaseClass + this.wp.hyphen + this.wp.containerLabel ).remove();
		},
		_setWidgetState: function() {
			var _widgetState = {
	    			page: 1,
	    			html: null,
	    			startIndex: 1,
	    			endIndex: 1,
	    			totalResults: 0,
	    			paginationFooter: "",
	    			highlightedIndex: 1,
	    			maximumNumberOfPages: 0,
	    			data: null,
	    			fetchedData: null
				};
			this.element.data({"widgetState": _widgetState});
			this._constructPaginationFooterTemplate();
		},
		_getWidgetState: function() {
			return this.element.data("widgetState");
		},
		_resetWidgetState: function() {
			this._setWidgetState();
		},
	    _fireEvent: function(eventName, event, data) {
	    	if(eventName === this.wp.itemSelectedEvent && !data) {
	    		data = this._getSelectedItemObject();
	    	}
	    	this._resetWidgetState();
			this._trigger(eventName, event, data);
	    },
	    _getSelectedItemObject: function() {
	    	var data = {},
	    		widgetState = this._getWidgetState(),
	    		od = widgetState.fetchedData;
    		if(od && od.length > 0) {
    			data = od[widgetState.startIndex + widgetState.highlightedIndex - 1];
    		}
    		return data;
	    },
	    _setOption: function( key, value ) {
	    	if(this._super) {
	    		this._super( key, value );
	    		return;
	    	}
	        $.Widget.prototype._setOption.apply( this, arguments );
	    },
	    _setOptions: function( options ) {
	    	if(this._super) {
		        this._super( options );
	    		return;
	    	}
	    	$.Widget.prototype._setOptions.apply( this, arguments );
	    },
	    destroy: function() {
	    	this._cleanup();
	    	this.element.removeClass( this.wp.widgetBaseClass ).unwrap();
	    }
	});
}(jQuery));